Give it a world.
I built the asset library first: ghosts, scenery and a visual style. The park gave the technical experiments something concrete to grow around.
Build the park.
Please the dead.
A ghost-filled park, built from the ground up. I create the art, develop the simulation and build the tools that bring it all together.
A world made from scratch.
A visual world first. A place to explore art and engineering ever since.
I built the asset library first: ghosts, scenery and a visual style. The park gave the technical experiments something concrete to grow around.
Crowd behaviour, GPU animation and procedural motion became connected problems, each with its own authoring and debugging tools.
The audio experiments developed into ImpulZ. They are now the main focus, moving towards a standalone application beyond Unity.
Some tools solve an immediate need in the park. Others start with a question I want to explore. Working in the same setting lets me connect those ideas instead of leaving them as separate technical demos.
Nightfair gives me room to practise unfamiliar problems, from a ghost's next decision to the way a camera carries momentum or a sound travels through a doorway. The systems below show both the results and the tools I use to inspect them.
Simulation, animation, tools and sound.
A closer look at how the park works.
A park full of individual decisions.
A data-oriented simulation for 10,000+ active agents, with tools to inspect the crowd and follow the decisions of a single ghost.
Complex NPC behaviour becomes expensive at large population sizes. Transform updates and scattered object memory can saturate the main thread before the simulation reaches the intended scale.
I moved agent data into a Structure of Arrays layout and run decisions in Burst-compiled Jobs. A 128-bit filter quickly removes irrelevant intents before the more expensive evaluation work.
Parallel jobs need a consistent view of agent state. Double buffering gives each simulation step a stable snapshot and avoids race conditions without mutex-based locking.
Animation that runs on the GPU.
A vertex animation texture pipeline that bakes skeletal motion into textures, keeping large animated crowds compatible with GPU instancing.
Evaluating thousands of skinned meshes consumes CPU time and memory, and prevents the crowd from using a simple instanced rendering path.
I bake vertex motion into relative local-space offsets. The pipeline stores these in 8-bit textures and reconstructs the animation on the GPU, removing runtime skeletal evaluation.
Low-bit-depth animation can introduce jitter and inaccurate normals. Bounds-based range remapping preserves positional precision, while octahedral encoding stores a normal in two channels for reconstruction in the shader.
Giving floating ghosts a sense of weight.
A motion-authoring tool for assets without runtime skeletal data, using editable curves to add procedural movement and variation.
Instanced VAT characters have no runtime bones to drive their movement. Without a separate motion layer, they can look as though they are gliding through the scene.
I built a procedural motion system around Bézier curves. The authoring tool bakes these curves into 256-step lookup tables so multi-threaded Jobs can sample displacement in constant time.
CPU movement has to stay synchronized with GPU animation playback. A custom visual interface makes the curve-based system easier to author while keeping the preview aligned with runtime results.
A camera with momentum and intent.
A physics-driven camera for exploring the park, combining a gimbal rig with responsive controls across different viewing distances.
Large-scale management cameras can feel detached, and fast movement becomes uncomfortable without suitable damping. Nightfair's world bending adds another reason to give the camera a coherent physical response.
I built a G-force gimbal that uses centripetal acceleration to drive camera lag, shake and field-of-view changes, giving movement a drone-like sense of momentum.
The camera still needs to respond predictably while carrying momentum. Nonlinear Hermite-spline sensitivity scaling and edge-case snapping keep panning usable at different zoom altitudes.
Keeping branching conversations connected.
A visual dialogue graph and localization workflow that keep narrative structure, text and translation context together.
Large branching narratives make it easy to lose track of dialogue keys, break links or leave translators without the visual context they need.
The dialogue graph serves as the shared source for narrative logic and localization. It compiles into a flat runtime structure, while Translator Vision captures visual context for localization review.
The graph and its localized text need to stay valid as assets change. A Unicode CLDR rule engine and asset post-processing validate the structure and rich-text formatting.
Keep the paths you did not take.
A branching undo and redo system that preserves alternative edits, allowing tools to revisit different versions of a creative decision.
A conventional undo stack discards the redo history as soon as a new edit is made. That makes it difficult to compare alternate audio edits or camera paths without losing earlier work.
The UndoRedoManager stores history as a branching graph. New edits create another branch instead of replacing an existing future, so tools can travel between alternative states.
Branches increase memory use and can become inconsistent with live state. Serialized delta budgeting prunes the oldest leaf nodes, while state hashes check integrity before restoration.
Make a busy simulation readable.
A shared logging and command layer for inspecting AI, grids, audio and runtime state without losing the source of a diagnostic message.
Diagnostic messages from many systems create noise and overhead. Logging wrappers can also obscure the call site, making Unity's double-click source navigation point to the wrapper instead of the real issue.
I built GLog, a dependency-free telemetry layer with atomic bitmask filtering, and a shared console service for commands and simulation inspection. Disabled log categories exit before string formatting or allocation.
The output needs to stay readable while preserving accurate call stacks. Editor-driven stack-trace configuration hides the logging wrapper and retains full exception traces.
Organic layouts, connected underneath.
A procedural grid that creates irregular park layouts while retaining a connected structure for simulation and navigation.
Square and hexagonal grids can look artificial. Generating irregular chunks separately can also leave mismatched boundaries, gaps and T-junctions.
I combine Lloyd relaxation with Conway Ortho subdivision to create mostly quadrilateral topology with organic cul-de-sacs. A seeded noise frontier keeps independently generated chunk boundaries aligned.
Asynchronously generated chunks must join reliably. World-space noise blending and positional quantization let the rolling grid identify and weld shared boundary vertices.
A sound system that grew into its own project.
A custom audio ecosystem for spatial emitters, sound design and mixing, with a timeline, modulation graph, mixer and dedicated diagnostic tools.
Dense simulations can turn many concurrent emitters into an indistinct mix while source updates and allocations strain the runtime.
I built a job-driven audio pipeline, moving muffling, air absorption and diffraction work into batched raycasts and worker-thread processing. Authoring separates inexpensive real-time operations from more expensive baked processing so sounds remain editable.
Moving sources need synchronized playback without repeated allocations. A prewarmed generational pool and HDR-based voice virtualization keep source lifecycles controlled while prioritizing audible content.
Best experienced with headphones. Start at a low volume; some samples contain strong transients.
1st example
2nd example
3rd: Ambient procedural
4th: MCP with choir samples
Audio objects, the main mix and background beds.
A moving audio object using the internal spatialization engine.
Compare the original flare-gun recording with the binaural convolution reverb.
The unmodified flare-gun source, before convolution reverb.
The flare-gun source as a binaural audio object with a long reverb tail.